package com.bluexmicro.ota2.ui.screen.config

import android.app.Application
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
import android.widget.Toast
import com.bluexmicro.bluetooth.BluetoothCentral
import com.bluexmicro.bluetooth.model.WriteType
import com.bluexmicro.bluetooth.peripheral.ConnectionState
import com.bluexmicro.bluetooth.peripheral.PeripheralManager
import com.bluexmicro.bluetooth.toHexString
import com.bluexmicro.ota.asset.AssetProcess
import com.bluexmicro.ota2.ui.screen.config.bxlink.BxLinkMessage
import com.bluexmicro.ota2.ui.screen.config.bxlink.BxLinkProtocol
import com.bluexmicro.ota2.ui.screen.config.bxlink.CommonUtils
import com.bluexmicro.ota2.ui.screen.config.bxlink.MessageQueueManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.Calendar
import java.util.TimeZone
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean


object YaraCore {

    private const val TAG = "YaraCore"
    private const val uuid_service_bxlink = "0000ff00-0000-1000-8000-00805f9b34fb"
    private const val uuid_characteristic_command = "0000ff01-0000-1000-8000-00805f9b34fb"


    //SupervisorJob创建了一个新的作业对象，允许其子协程独立地失败和恢复，不会影响其他子协程。
    private val mScope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    //    private lateinit var app: Application
    private val messageHandler = YaraMessageHandler(mScope)

    private var mPeripheral: PeripheralManager.BluetoothPeripheral? = null
    private var mHandler: BluetoothGattCharacteristic? = null
    private var backgroundHandler: AssetProcess.Process? = null


    private val _deviceReady = MutableStateFlow(false)
    val deviceReady = _deviceReady.asStateFlow()


    fun resetConnection() {
        backgroundHandler = null
        mPeripheral?.apply {
            BluetoothCentral.releasePeripheral(this)
        }
        messageHandler.onDisconnected()
        //初始化
        mPeripheral = null
        mHandler = null
        listeningConnectionJob?.takeIf { it.isActive }?.apply { cancel() }
        setupConnectionJob?.takeIf { it.isActive }?.apply { cancel() }
        connectRequested.set(false)
        _deviceReady.value = false
    }

    fun tryConnect(context: Context, address: String) = mScope.launch(Dispatchers.Main) {
        if (connectRequested.compareAndSet(false, true)) {
            Log.e(TAG, "tryConnect")
            val peripheral =
                BluetoothCentral.retrievePeripherals(context, address) ?: return@launch
            try {
                listeningConnection(peripheral)
                BluetoothCentral.connect(context, peripheral, true)
                mPeripheral = peripheral
            } catch (e: Exception) {
                retry(peripheral)
                e.printStackTrace()
            }
        }
    }

    private var listeningConnectionJob: Job? = null
    private fun listeningConnection(peripheral: PeripheralManager.BluetoothPeripheral) {
        Log.d(TAG, "listeningConnection() called with: peripheral = ${peripheral.device.address}")
        listeningConnectionJob?.takeIf { it.isActive }?.apply { cancel() }
        listeningConnectionJob = mScope.launch(Dispatchers.Default) {
            //非主线程
            peripheral.state.collect {
                Log.w(TAG, "${peripheral.device.address}: $it")
                if (it == ConnectionState.CONNECTED) {
                    setupConnection(peripheral)
                } else {
                    messageHandler.onDisconnected()
                    setupConnectionJob?.takeIf { job -> job.isActive }?.apply { cancel() }
                    _deviceReady.value = false
                    connectRequested.set(false)
                }
            }
        }
    }

    private var setupConnectionJob: Job? = null
    private fun setupConnection(peripheral: PeripheralManager.BluetoothPeripheral) {
        Log.e(TAG, "setupConnection: $mPeripheral")
        backgroundHandler = null
        setupConnectionJob?.takeIf { it.isActive }?.apply { cancel() }
        setupConnectionJob = mScope.launch {
            peripheral.apply {

                try {
                    requestMTU(251)
                } catch (e: Exception) {
                    e.printStackTrace()
                }

                val services = try {
                    withTimeout(3000) {
                        discoverServices().getOrNull()
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    emptyList()
                }
                val service = services?.find { it.uuid.equals(UUID.fromString(uuid_service_bxlink)) }
                val handler =
                    service?.getCharacteristic(UUID.fromString(uuid_characteristic_command))

                val ready = if (handler != null) {
                    try {
                        setNotification(handler, true)
                        true
                    } catch (e: Exception) {
                        e.printStackTrace()
                        false
                    }
                } else {
                    false
                }
                if (ready) {
                    mHandler = handler
                    _deviceReady.value = true
                    messageHandler.onReady(this, handler!!)
                } else {
                    retry(this)
                }
            }
        }
    }

    private fun retry(peripheral: PeripheralManager.BluetoothPeripheral) = mScope.launch {
        Log.e(TAG, "retry: ")
        _deviceReady.value = false
        messageHandler.onDisconnected()
        connectRequested.compareAndSet(true, false)
        try {
            BluetoothCentral.releasePeripheral(peripheral)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        //等待循环
    }


    ///////////////////////////////////////////////////////////////////////////
    //
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 是否需要请求连接
     */
    private val connectRequested = AtomicBoolean(false)


    fun debugPowers(context: Context, values: ByteArray) {
        if (checkConnection(context))
            messageHandler.debugPowers(values)
    }

    private fun checkConnection(context: Context): Boolean {
        if (deviceReady.value.not()) {
            mScope.launch(Dispatchers.Main) {
                Toast.makeText(context, "Disconnected!", Toast.LENGTH_SHORT).show()
            }
        }
        return deviceReady.value
    }

}


class YaraMessageHandler(private val scope: CoroutineScope) {


    companion object {
        private const val uuid_service_bxlink = "0000ff00-0000-1000-8000-00805f9b34fb"
        private const val uuid_characteristic_command = "0000ff01-0000-1000-8000-00805f9b34fb"
        private const val CODE_SYNC_TIME: Short = 0x0001
        private const val CODE_DEBUG_POWER: Short = 0x0008 //
    }


    /**
     * Bx-Link协议的实现，处理包头、分包、超时等
     */
    private val bxLink = object : BxLinkProtocol() {
        override fun onError(msg: String) {
            msgQueueManager.onError(msg)
        }

        override fun onReceivedBxLinkMessage(message: BxLinkMessage) {
            Log.w("TAG", "onReceivedBxLinkMessage: $message")
            msgQueueManager.onReceivedBxLinkMessage(message)
            if (message.code == 0xffff.toShort()) {
                val requestCode = message.payload.run {
                    CommonUtils.toUInt16(get(1), get(0)).toShort()
                }
            }
        }
    }

    private var msgJob: Job? = null
    fun onReady(
        peripheral: PeripheralManager.BluetoothPeripheral,
        commandHandler: BluetoothGattCharacteristic
    ) {
        this.mPeripheral = peripheral
        this.commandHandler = commandHandler
        msgQueueManager.setup(peripheral, commandHandler)

        syncTime()

        msgJob?.takeIf { it.isActive }?.apply { cancel() }
        msgJob = scope.launch(Dispatchers.Default) {
            val mac = peripheral.device.address
            BluetoothCentral.observeCharacteristicValue(
                mac, uuid_service_bxlink,
                uuid_characteristic_command
            ).collect {
                bxLink.collectPartMessage(commandHandler, it.value)
            }
        }
    }

    fun onDisconnected() {
        msgJob?.takeIf { it.isActive }?.apply { cancel() }
        msgQueueManager.onDisconnected()
        this.mPeripheral = null
        this.commandHandler = null
    }

    private var mPeripheral: PeripheralManager.BluetoothPeripheral? = null
    private var commandHandler: BluetoothGattCharacteristic? = null
    private val msgQueueManager = MessageQueueManager(scope) {
        Log.e("TAG", "msgQueueManager.invoke $it")
        try {
            val core = mPeripheral ?: throw IllegalArgumentException()
            val cmdHandler = commandHandler ?: throw IllegalArgumentException()
            val pendingPackages = BxLinkProtocol.splitBxLinkMessage(it.origin, core.mtuSize)
//            Log.d(TAG, "pendingPackages size: ${pendingPackages.size}")
            for (pkt in pendingPackages) {
                Log.d("TAG", "sendMessage: ${pkt.toHexString()}")
                core.write(cmdHandler, WriteType.WithResponse, pkt)
            }
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    }


    private fun sendMessage(msg: BxLinkMessage, highLevel: Boolean = false) {
        msgQueueManager.pushMessage(msg, highLevel)
    }

    private fun syncTime() = scope.launch(Dispatchers.Default) {
        val calendar = Calendar.getInstance()
        // 从Calendar实例中获取时区
        val timeZone: TimeZone = calendar.timeZone

        // 获取时区与UTC的偏差量（毫秒）
        val offsetInMillis: Int = timeZone.rawOffset
        // 将偏差量转换为小时
        val offsetHours = offsetInMillis / 3600000 // 3600000毫秒 = 1小时
        val utcTimestampSeconds = (calendar.time.time / 1000).toInt()

        Calendar.getInstance()
        val bb = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN)
        bb.putInt(utcTimestampSeconds)
        bb.put(offsetHours.toByte())
        val msg = BxLinkMessage(CODE_SYNC_TIME, bb.array(), 0)
        sendMessage(msg)
    }


    fun debugPowers(values: ByteArray) {
        val msg = BxLinkMessage(CODE_DEBUG_POWER, values, 0)
        sendMessage(msg)
    }

}